from django.contrib.auth import get_user_model
from django.template import Context, Template
from django.test import RequestFactory, TestCase
from django.urls import reverse
from django.utils import timezone
from django.utils.html import strip_tags

from ...core.tests import utils
from .forms import PollVoteManyForm
from .models import CommentPoll, CommentPollChoice, CommentPollVote, PollMode
from .utils import render
from .utils.render_static import post_render_static_polls

User = get_user_model()


class PollViewTest(TestCase):
    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.user2 = utils.create_user()
        self.category = utils.create_category()
        self.topic = utils.create_topic(self.category, user=self.user)
        self.topic2 = utils.create_topic(self.category, user=self.user2)
        self.comment = utils.create_comment(topic=self.topic)
        self.user_comment = utils.create_comment(topic=self.topic, user=self.user)
        self.poll = CommentPoll.objects.create(comment=self.comment, name="foo")
        self.poll_multi = CommentPoll.objects.create(
            comment=self.comment, name="bar", choice_max=2
        )

    def test_poll_close_logged_in(self):
        """
        User must be logged in
        """
        response = self.client.post(
            reverse("spirit:comment:poll:close", kwargs={"pk": self.poll.pk}), {}
        )
        self.assertEqual(response.status_code, 302)

    def test_poll_close_wrong_user(self):
        """
        Try to close another user poll should return 404
        """
        utils.login(self)
        response = self.client.post(
            reverse("spirit:comment:poll:close", kwargs={"pk": self.poll.pk}), {}
        )
        self.assertEqual(response.status_code, 404)

    def test_poll_close_get(self):
        """
        GET, poll_close
        """
        utils.login(self)
        response = self.client.get(
            reverse("spirit:comment:poll:close", kwargs={"pk": self.poll.pk})
        )
        self.assertEqual(response.status_code, 405)

    def test_poll_close_post(self):
        """
        POST, poll_close
        """
        utils.login(self)
        poll = CommentPoll.objects.create(comment=self.user_comment, name="foo")
        response = self.client.post(
            reverse("spirit:comment:poll:close", kwargs={"pk": poll.pk}), {}
        )
        expected_url = poll.get_absolute_url()
        self.assertRedirects(
            response, expected_url, status_code=302, target_status_code=302
        )
        self.assertTrue(CommentPoll.objects.get(pk=poll.pk).is_closed)

    def test_poll_close_open_post(self):
        """
        POST, poll_open
        """
        utils.login(self)
        poll = CommentPoll.objects.create(
            comment=self.user_comment, name="foo", close_at=timezone.now()
        )
        self.assertTrue(poll.is_closed)
        response = self.client.post(
            reverse("spirit:comment:poll:open", kwargs={"pk": poll.pk}), {}
        )
        expected_url = poll.get_absolute_url()
        self.assertRedirects(
            response, expected_url, status_code=302, target_status_code=302
        )
        self.assertFalse(CommentPoll.objects.get(pk=poll.pk).is_closed)

    def test_poll_vote_logged_in(self):
        """
        User must be logged in
        """
        response = self.client.post(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll.pk}), {}
        )
        expected_url = (
            reverse("spirit:user:auth:login") + "?next=" + self.poll.get_absolute_url()
        )
        self.assertRedirects(response, expected_url, status_code=302)

    def test_poll_vote_get(self):
        """
        GET, poll_vote
        Post is required
        """
        utils.login(self)
        response = self.client.get(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll.pk})
        )
        self.assertEqual(response.status_code, 405)

    def test_poll_vote_post(self):
        """
        POST, poll_vote
        """
        utils.login(self)
        choice = CommentPollChoice.objects.create(
            poll=self.poll, number=1, description="op1"
        )
        form_data = {"choices": choice.pk}
        response = self.client.post(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll.pk}), form_data
        )
        expected_url = self.poll.get_absolute_url()
        self.assertRedirects(
            response, expected_url, status_code=302, target_status_code=302
        )
        self.assertEqual(len(CommentPollVote.objects.filter(choice=choice)), 1)

    def test_poll_vote_post_invalid(self):
        """
        POST, poll_vote
        """
        utils.login(self)
        response = self.client.post(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll.pk}),
            {},
            follow=True,
        )
        self.assertEqual(len(response.context["messages"]), 1)  # error message

    def test_poll_vote_post_invalid_redirect(self):
        """
        POST, poll_vote
        """
        utils.login(self)
        response = self.client.post(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll.pk}), {}
        )
        expected_url = self.poll.get_absolute_url()
        self.assertRedirects(
            response, expected_url, status_code=302, target_status_code=302
        )

    def test_poll_vote_post_multi(self):
        """
        Should be able to vote many options
        """
        utils.login(self)
        choice_a = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=1, description="op a"
        )
        choice_b = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=2, description="op b"
        )
        CommentPollChoice.objects.create(
            poll=self.poll_multi, number=3, description="op c"
        )

        form_data = {"choices": [choice_a.pk, choice_b.pk]}
        response = self.client.post(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll_multi.pk}),
            form_data,
        )
        expected_url = self.poll.get_absolute_url()

        self.assertRedirects(
            response, expected_url, status_code=302, target_status_code=302
        )
        self.assertEqual(len(CommentPollVote.objects.all()), 2)
        self.assertEqual(len(CommentPollVote.objects.filter(choice=choice_a.pk)), 1)
        self.assertEqual(len(CommentPollVote.objects.filter(choice=choice_b.pk)), 1)

    def test_poll_vote_post_count(self):
        """
        Should increase the vote counters
        """
        utils.login(self)
        choice_a = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=1, description="op a"
        )
        choice_b = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=2, description="op b"
        )
        choice_c = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=3, description="op c"
        )

        form_data = {"choices": [choice_a.pk, choice_b.pk]}
        response = self.client.post(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll_multi.pk}),
            form_data,
        )
        expected_url = self.poll.get_absolute_url()

        self.assertRedirects(
            response, expected_url, status_code=302, target_status_code=302
        )
        self.assertEqual(CommentPollChoice.objects.get(pk=choice_a.pk).vote_count, 1)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice_b.pk).vote_count, 1)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice_c.pk).vote_count, 0)

        form_data = {"choices": [choice_a.pk]}
        response = self.client.post(
            reverse("spirit:comment:poll:vote", kwargs={"pk": self.poll_multi.pk}),
            form_data,
        )
        expected_url = self.poll.get_absolute_url()

        self.assertRedirects(
            response, expected_url, status_code=302, target_status_code=302
        )
        self.assertEqual(CommentPollChoice.objects.get(pk=choice_a.pk).vote_count, 1)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice_b.pk).vote_count, 0)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice_c.pk).vote_count, 0)

    def test_poll_voters_logged_in(self):
        """
        User must be logged in
        """
        poll_choice = CommentPollChoice.objects.create(
            poll=self.poll, number=1, description="op1"
        )
        response = self.client.get(
            reverse("spirit:comment:poll:voters", kwargs={"pk": poll_choice.pk})
        )
        self.assertEqual(response.status_code, 302)

    def test_poll_voters(self):
        """
        Should query choice voters
        """
        poll_choice = CommentPollChoice.objects.create(
            poll=self.poll, number=1, description="op1"
        )
        poll_choice2 = CommentPollChoice.objects.create(
            poll=self.poll, number=2, description="op2"
        )
        vote = CommentPollVote.objects.create(voter=self.user, choice=poll_choice)
        CommentPollVote.objects.create(voter=self.user2, choice=poll_choice2)

        utils.login(self)
        response = self.client.get(
            reverse("spirit:comment:poll:voters", kwargs={"pk": poll_choice.pk})
        )
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.context["choice"], poll_choice)
        self.assertEqual(list(response.context["votes"]), [vote])

    def test_poll_voters_secret(self):
        """
        Should forbid view voters of secret poll when is not closed
        """
        poll = CommentPoll.objects.create(
            comment=self.comment, name="foobar", mode=PollMode.SECRET
        )
        poll_choice = CommentPollChoice.objects.create(
            poll=poll, number=1, description="op1"
        )

        utils.login(self)
        response = self.client.get(
            reverse("spirit:comment:poll:voters", kwargs={"pk": poll_choice.pk})
        )
        self.assertEqual(response.status_code, 403)

    def test_poll_voters_secret_closed(self):
        """
        Should allow view voters of secret poll when is closed
        """
        yesterday = timezone.now() - timezone.timedelta(days=1)
        poll = CommentPoll.objects.create(
            comment=self.comment,
            name="foobar",
            mode=PollMode.SECRET,
            close_at=yesterday,
        )
        poll_choice = CommentPollChoice.objects.create(
            poll=poll, number=1, description="op1"
        )

        utils.login(self)
        response = self.client.get(
            reverse("spirit:comment:poll:voters", kwargs={"pk": poll_choice.pk})
        )
        self.assertEqual(response.status_code, 200)


class PollFormTest(TestCase):
    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.user2 = utils.create_user()
        self.category = utils.create_category()
        self.topic = utils.create_topic(self.category, user=self.user)
        self.comment = utils.create_comment(topic=self.topic)
        self.comment2 = utils.create_comment(topic=self.topic)

        # Single choice
        self.poll = CommentPoll.objects.create(comment=self.comment, name="foo")

        self.poll_choice = CommentPollChoice.objects.create(
            poll=self.poll, number=1, description="op1"
        )
        self.poll_choice2 = CommentPollChoice.objects.create(
            poll=self.poll, number=2, description="op2"
        )

        self.poll_vote = CommentPollVote.objects.create(
            voter=self.user, choice=self.poll_choice
        )
        self.poll_vote2 = CommentPollVote.objects.create(
            voter=self.user2, choice=self.poll_choice
        )

        # ...poor man prefetch
        self.poll_choice.votes = [self.poll_vote]
        self.poll.choices = [self.poll_choice, self.poll_choice2]

        # Multi choice
        self.poll_multi = CommentPoll.objects.create(
            comment=self.comment2, name="bar", choice_max=2
        )

        self.poll_multi_choice = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=1, description="op1"
        )
        self.poll_multi_choice2 = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=2, description="op2"
        )
        self.poll_multi_choice3 = CommentPollChoice.objects.create(
            poll=self.poll_multi, number=3, description="op3"
        )

        self.poll_multi_vote = CommentPollVote.objects.create(
            voter=self.user, choice=self.poll_multi_choice
        )
        self.poll_multi_vote2 = CommentPollVote.objects.create(
            voter=self.user, choice=self.poll_multi_choice2
        )
        self.poll_multi_vote3 = CommentPollVote.objects.create(
            voter=self.user2, choice=self.poll_multi_choice
        )

        # ...poor man prefetch
        self.poll_multi_choice.votes = [self.poll_multi_vote]
        self.poll_multi_choice2.votes = [self.poll_multi_vote2]
        self.poll_multi.choices = [self.poll_multi_choice, self.poll_multi_choice2]

    def test_vote_load_initial_single(self):
        """
        TopicPollVoteManyForm
        """
        form = PollVoteManyForm(user=self.user, poll=self.poll)
        form.load_initial()
        self.assertEqual(form.initial, {"choices": self.poll_choice.pk})

    def test_vote_load_initial_multi(self):
        """
        TopicPollVoteManyForm
        """
        form = PollVoteManyForm(user=self.user, poll=self.poll_multi)
        form.load_initial()
        self.assertDictEqual(
            form.initial,
            {"choices": [self.poll_multi_choice.pk, self.poll_multi_choice2.pk]},
        )

    def test_vote_load_initial_empty(self):
        """
        TopicPollVoteManyForm
        """
        CommentPollVote.objects.all().delete()
        self.poll_choice.votes = []

        form = PollVoteManyForm(user=self.user, poll=self.poll)
        form.load_initial()
        self.assertEqual(form.initial, {})

    def test_vote_load_initial_choice_limit(self):
        """
        Load initial for a single choice poll that was previously a multi choice poll
        """
        # multi to single
        self.poll_multi.choice_max = 1

        form = PollVoteManyForm(user=self.user, poll=self.poll_multi)
        form.load_initial()
        self.assertDictEqual(form.initial, {"choices": self.poll_multi_choice.pk})

        # single to multi
        self.poll.choice_max = 2

        form = PollVoteManyForm(user=self.user, poll=self.poll)
        form.load_initial()
        self.assertDictEqual(form.initial, {"choices": [self.poll_choice.pk]})

    def test_vote_poll_closed(self):
        """
        Cant vote on closed poll
        """
        self.poll.close_at = timezone.now()
        self.poll.save()

        form_data = {"choices": self.poll_choice.pk}
        form = PollVoteManyForm(user=self.user, poll=self.poll, data=form_data)
        self.assertFalse(form.is_valid())

    def test_create_vote_single(self):
        """
        TopicPollVoteManyForm
        """
        CommentPollVote.objects.all().delete()

        form_data = {"choices": self.poll_choice.pk}
        form = PollVoteManyForm(user=self.user, poll=self.poll, data=form_data)
        self.assertTrue(form.is_valid())
        form.save_m2m()
        self.assertEqual(len(CommentPollVote.objects.all()), 1)
        self.assertEqual(
            len(
                CommentPollVote.objects.filter(
                    choice=self.poll_choice, is_removed=False
                )
            ),
            1,
        )

    def test_create_vote_multi(self):
        """
        TopicPollVoteManyForm
        """
        CommentPollVote.objects.all().delete()
        self.poll_multi_choice.votes = []
        self.poll_multi_choice2.votes = []

        form_data = {"choices": [self.poll_multi_choice.pk, self.poll_multi_choice2.pk]}
        form = PollVoteManyForm(user=self.user, poll=self.poll_multi, data=form_data)
        self.assertTrue(form.is_valid())

    def test_create_vote_multi_invalid(self):
        """
        Limit selected choices to choice_limit
        """
        CommentPollVote.objects.all().delete()
        self.poll_multi_choice.votes = []
        self.poll_multi_choice2.votes = []

        form_data = {
            "choices": [
                self.poll_multi_choice.pk,
                self.poll_multi_choice2.pk,
                self.poll_multi_choice3.pk,
            ]
        }
        form = PollVoteManyForm(user=self.user, poll=self.poll_multi, data=form_data)
        self.assertFalse(form.is_valid())

    def test_update_vote_single(self):
        """
        TopicPollVoteManyForm
        """
        self.assertEqual(
            len(
                CommentPollVote.objects.filter(
                    choice=self.poll_choice2, is_removed=False
                )
            ),
            0,
        )
        self.assertEqual(
            len(
                CommentPollVote.objects.filter(
                    choice=self.poll_choice, is_removed=False
                )
            ),
            2,
        )

        form_data = {"choices": self.poll_choice2.pk}
        form = PollVoteManyForm(user=self.user, poll=self.poll, data=form_data)
        self.assertTrue(form.is_valid())
        form.save_m2m()
        self.assertEqual(
            len(
                CommentPollVote.objects.filter(
                    choice=self.poll_choice2, is_removed=False
                )
            ),
            1,
        )
        self.assertEqual(
            len(
                CommentPollVote.objects.filter(
                    choice=self.poll_choice, is_removed=False
                )
            ),
            1,
        )


class CommentPollTemplateTagsTest(TestCase):
    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.category = utils.create_category()
        self.topic = utils.create_topic(category=self.category)
        self.user_comment = utils.create_comment(
            topic=self.topic, user=self.user, comment_html="<poll name=foo>"
        )
        self.user_poll = CommentPoll.objects.create(
            comment=self.user_comment, name="foo"
        )
        self.user_comment_with_polls = (
            self.user_comment.__class__.objects.filter(pk=self.user_comment.pk)
            .with_polls(self.user)
            .first()
        )

        self.request = RequestFactory().get("/")
        self.request.user = self.user

    def test_render_polls_form(self):
        """
        Should display poll vote form
        """
        res = []

        def mock_render_to_string(tlt, ctx):
            res.append(tlt)
            res.append(ctx)

        org_render_to_string, render.render_to_string = (
            render.render_to_string,
            mock_render_to_string,
        )
        try:
            render.render_polls(
                self.user_comment_with_polls, self.request, "csrf_token_foo"
            )
            self.assertEqual(len(res), 2)
            template, context = res[0], res[1]
            self.assertEqual(template, "spirit/comment/poll/_form.html")
            self.assertEqual(context["form"].poll, self.user_poll)
            self.assertIsInstance(context["poll"], CommentPoll)
            self.assertEqual(context["user"], self.user)
            self.assertEqual(context["comment"], self.user_comment_with_polls)
            self.assertEqual(context["request"], self.request)
            self.assertEqual(context["csrf_token"], "csrf_token_foo")
        finally:
            render.render_to_string = org_render_to_string

    def test_render_polls_template_form(self):
        """
        Should display poll vote form
        """
        out = Template(
            "{% load spirit_tags %}{% post_render_comment comment=comment %}"
        ).render(
            Context(
                {
                    "comment": self.user_comment_with_polls,
                    "request": self.request,
                    "csrf_token": "foo",
                }
            )
        )
        self.assertNotEqual(out.strip(), "")
        self.assertTrue("<poll" not in out)
        form_id = 'id="p%s"' % self.user_poll.pk
        self.assertTrue(form_id in out)
        show_link = "?show_poll={pk}#p{pk}".format(pk=self.user_poll.pk)
        self.assertTrue(show_link in out)

    def test_render_polls_template_form_not_author(self):
        """
        Should display poll vote form
        """
        request = RequestFactory().get("/")
        request.user = utils.create_user()
        out = Template(
            "{% load spirit_tags %}{% post_render_comment comment=comment %}"
        ).render(
            Context(
                {
                    "comment": self.user_comment_with_polls,
                    "request": request,
                    "csrf_token": "foo",
                }
            )
        )
        self.assertNotEqual(out.strip(), "")
        form_id = 'id="p%s"' % self.user_poll.pk
        self.assertTrue(form_id in out)

    def test_render_polls_template_form_close(self):
        """
        Should display the close button
        """
        out = Template(
            "{% load spirit_tags %}{% post_render_comment comment=comment %}"
        ).render(
            Context(
                {
                    "comment": self.user_comment_with_polls,
                    "request": self.request,
                    "csrf_token": "foo",
                }
            )
        )
        self.assertNotEqual(out.strip(), "")
        close_link = reverse(
            "spirit:comment:poll:close", kwargs={"pk": self.user_poll.pk}
        )
        self.assertTrue(close_link in out)

    def test_render_polls_template_form_close_not_author(self):
        """
        Should *not* display the close button to not poll author
        """
        request = RequestFactory().get("/")
        request.user = utils.create_user()
        out = Template(
            "{% load spirit_tags %}{% post_render_comment comment=comment %}"
        ).render(
            Context(
                {
                    "comment": self.user_comment_with_polls,
                    "request": request,
                    "csrf_token": "foo",
                }
            )
        )
        self.assertNotEqual(out.strip(), "")
        close_link = reverse(
            "spirit:comment:poll:close", kwargs={"pk": self.user_poll.pk}
        )
        self.assertTrue(close_link not in out)

    def test_render_polls_template_form_open(self):
        """
        Should display the open button
        """
        self.user_comment_with_polls.polls[
            0
        ].close_at = timezone.now()  # renders results.html

        out = Template(
            "{% load spirit_tags %}{% post_render_comment comment=comment %}"
        ).render(
            Context(
                {
                    "comment": self.user_comment_with_polls,
                    "request": self.request,
                    "csrf_token": "foo",
                }
            )
        )
        self.assertNotEqual(out.strip(), "")
        open_link = reverse(
            "spirit:comment:poll:open", kwargs={"pk": self.user_poll.pk}
        )
        self.assertTrue(open_link in out)

    def test_render_polls_secret(self):
        """
        Should not display the view results link when poll is secret and is not closed
        """
        comment = utils.create_comment(topic=self.topic, comment_html="<poll name=bar>")
        CommentPoll.objects.create(comment=comment, name="bar", mode=PollMode.SECRET)
        user_comment_with_polls = (
            comment.__class__.objects.filter(pk=comment.pk)
            .with_polls(self.user)
            .first()
        )

        out = Template(
            "{% load spirit_tags %}{% post_render_comment comment=comment %}"
        ).render(
            Context(
                {
                    "comment": user_comment_with_polls,
                    "request": self.request,
                    "csrf_token": "foo",
                }
            )
        )
        self.assertNotEqual(out.strip(), "")
        self.assertFalse("Show results" in out)
        self.assertTrue("form" in out)

    def test_render_polls_secret_closed(self):
        """
        Should display the results when poll is secret and is closed
        """
        comment = utils.create_comment(topic=self.topic, comment_html="<poll name=bar>")
        yesterday = timezone.now() - timezone.timedelta(days=1)
        CommentPoll.objects.create(
            comment=comment, name="bar", mode=PollMode.SECRET, close_at=yesterday
        )
        user_comment_with_polls = (
            comment.__class__.objects.filter(pk=comment.pk)
            .with_polls(self.user)
            .first()
        )

        out = Template(
            "{% load spirit_tags %}{% post_render_comment comment=comment %}"
        ).render(
            Context(
                {
                    "comment": user_comment_with_polls,
                    "request": self.request,
                    "csrf_token": "foo",
                }
            )
        )
        self.assertNotEqual(out.strip(), "")
        self.assertFalse("show_poll=" in out)
        self.assertFalse("form" in out)
        self.assertTrue("comment__text__poll" in out)


class PollModelsTest(TestCase):
    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.category = utils.create_category()
        self.topic = utils.create_topic(category=self.category, user=self.user)
        self.comment = utils.create_comment(topic=self.topic)

        self.poll = CommentPoll.objects.create(comment=self.comment, name="foo")
        self.choice = CommentPollChoice.objects.create(
            poll=self.poll, number=1, description=1
        )
        self.vote = CommentPollVote.objects.create(choice=self.choice, voter=self.user)

        # Kinda like comment.with_polls()
        self.poll.choices = list(CommentPollChoice.objects.filter(poll=self.poll))

        for c in self.poll.choices:
            c.votes = list(CommentPollVote.objects.filter(choice=c, voter=self.user))

    def test_poll_is_multiple_choice(self):
        """
        Should be true when max > 1
        """
        poll = CommentPoll.objects.create(
            comment=self.comment, name="bar", choice_max=2
        )
        self.assertFalse(self.poll.is_multiple_choice)
        self.assertTrue(poll.is_multiple_choice)

    def test_poll_has_choice_min(self):
        """
        Should be true when min > 1
        """
        poll = CommentPoll.objects.create(
            comment=self.comment, name="bar", choice_min=2
        )
        self.assertFalse(self.poll.has_choice_min)
        self.assertTrue(poll.has_choice_min)

    def test_poll_is_closed(self):
        """
        Should be true when close_at > now
        """
        yesterday = timezone.now() - timezone.timedelta(days=1)
        tomorrow = timezone.now() + timezone.timedelta(days=1)
        poll_old = CommentPoll.objects.create(
            comment=self.comment, name="bar", close_at=yesterday
        )
        poll_new = CommentPoll.objects.create(
            comment=self.comment, name="bar2", close_at=tomorrow
        )
        self.assertFalse(self.poll.is_closed)
        self.assertTrue(poll_old.is_closed)
        self.assertFalse(poll_new.is_closed)

    def test_poll_has_user_voted(self):
        """
        Should be true when the user has voted
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="bar")
        CommentPollChoice.objects.create(poll=poll, number=1, description=1)
        poll.choices = list(CommentPollChoice.objects.filter(poll=poll))

        for c in poll.choices:
            c.votes = []

        self.assertTrue(self.poll.has_user_voted)
        self.assertFalse(poll.has_user_voted)

    def test_poll_mode_txt(self):
        """
        Should return the mode description
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="bar")
        self.assertEqual(poll.mode_txt, "default")

        poll = CommentPoll.objects.create(
            comment=self.comment, name="bar2", mode=PollMode.SECRET
        )
        self.assertEqual(poll.mode_txt, "secret")

    def test_poll_total_votes(self):
        """
        Should return the total votes
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="bar")
        CommentPollChoice.objects.create(
            poll=poll, number=1, description="foo", vote_count=5
        )
        CommentPollChoice.objects.create(
            poll=poll, number=2, description="bar", vote_count=5
        )
        poll.choices = list(CommentPollChoice.objects.filter(poll=poll))
        self.assertEqual(poll.total_votes, 10)

    def test_poll_is_secret(self):
        """
        Should return whether the poll is secret or not
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="bar")
        self.assertFalse(poll.is_secret)
        poll.mode = PollMode.SECRET
        self.assertTrue(poll.is_secret)

    def test_poll_can_show_results(self):
        """
        Should return whether the poll results can be shown or not depending on the mode
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="bar")
        self.assertTrue(poll.can_show_results)
        poll.mode = PollMode.SECRET
        self.assertFalse(poll.can_show_results)
        yesterday = timezone.now() - timezone.timedelta(days=1)
        poll.close_at = yesterday
        self.assertTrue(poll.can_show_results)

    def test_poll_update_or_create_many(self):
        """
        Should create or update many polls for a given comment
        """
        poll_raw = {
            "name": "foo_raw",
            "title": "foo",
            "choice_min": 2,
            "choice_max": 2,
            "close_at": timezone.now(),
            "mode": PollMode.SECRET,
        }
        CommentPoll.update_or_create_many(comment=self.comment, polls_raw=[poll_raw])
        poll = CommentPoll.objects.all().order_by("pk").last()
        self.assertEqual(poll.name, poll_raw["name"])
        self.assertEqual(poll.title, poll_raw["title"])
        self.assertEqual(poll.choice_min, poll_raw["choice_min"])
        self.assertEqual(poll.choice_max, poll_raw["choice_max"])
        self.assertEqual(poll.close_at, poll_raw["close_at"])
        self.assertEqual(poll.mode, poll_raw["mode"])

        # Update
        CommentPoll.update_or_create_many(
            comment=self.comment, polls_raw=[{"name": poll.name, "title": "bar"}]
        )
        poll_updated = CommentPoll.objects.all().order_by("pk").last()
        self.assertEqual(poll.pk, poll_updated.pk)
        self.assertEqual(poll_updated.title, "bar")

    def test_poll_update_or_create_many_update_un_remove(self):
        """
        Should mark the poll as not removed on update
        """
        poll = CommentPoll.objects.create(
            comment=self.comment, name="foo_rm", is_removed=True
        )
        CommentPoll.update_or_create_many(
            comment=poll.comment, polls_raw=[{"name": poll.name}]
        )
        poll_updated = CommentPoll.objects.all().order_by("pk").last()
        self.assertEqual(poll.pk, poll_updated.pk)
        self.assertFalse(poll_updated.is_removed)

    def test_poll_choice_vote(self):
        """
        Should return the user vote for a given choice
        """
        choice = CommentPollChoice.objects.create(
            poll=self.poll, number=5, description="foobar"
        )
        vote = CommentPollVote.objects.create(choice=choice, voter=self.user)
        choice.votes = list(
            CommentPollVote.objects.filter(choice=choice, voter=self.user)
        )
        self.assertEqual(choice.vote, vote)
        choice.votes = []
        self.assertIsNone(choice.vote)
        del choice.votes
        self.assertIsNone(choice.vote)
        choice.votes = [vote, vote]
        self.assertRaises(AssertionError, lambda: choice.vote)

    def test_poll_choice_votes_percentage(self):
        """
        Should return the percentage of votes for a choice
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="percentage")
        choice = CommentPollChoice.objects.create(
            poll=poll, number=1, description="foobar", vote_count=1
        )
        poll.total_votes = 2
        self.assertEqual(choice.votes_percentage, 50)
        poll.total_votes = 3
        self.assertEqual(f"{choice.votes_percentage:.2f}", "33.33")
        poll.total_votes = 0
        self.assertEqual(choice.votes_percentage, 0)

    def test_poll_choice_increase_vote_count(self):
        """
        Should increase the vote count of all choices for a given user and poll
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="percentage")
        choice = CommentPollChoice.objects.create(
            poll=poll, number=1, description="foobar"
        )
        choice2 = CommentPollChoice.objects.create(
            poll=poll, number=2, description="foobar"
        )
        CommentPollVote.objects.create(choice=choice, voter=self.user)
        CommentPollVote.objects.create(choice=choice2, voter=self.user)
        user2 = utils.create_user()
        CommentPollVote.objects.create(choice=choice, voter=user2)

        CommentPollChoice.increase_vote_count(poll, self.user)
        self.assertEqual(CommentPollChoice.objects.get(pk=self.choice.pk).vote_count, 0)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice.pk).vote_count, 1)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice2.pk).vote_count, 1)

        CommentPollChoice.objects.filter(pk=choice.pk).update(is_removed=True)
        CommentPollChoice.increase_vote_count(poll, self.user)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice.pk).vote_count, 1)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice2.pk).vote_count, 2)

    def test_poll_choice_decrease_vote_count(self):
        """
        Should decrease the vote count of all choices for a given user and poll
        """
        poll = CommentPoll.objects.create(comment=self.comment, name="percentage")
        choice = CommentPollChoice.objects.create(
            poll=poll, number=1, description="foobar", vote_count=2
        )
        choice2 = CommentPollChoice.objects.create(
            poll=poll, number=2, description="foobar", vote_count=2
        )
        CommentPollVote.objects.create(choice=choice, voter=self.user)
        CommentPollVote.objects.create(choice=choice2, voter=self.user)
        user2 = utils.create_user()
        CommentPollVote.objects.create(choice=choice, voter=user2)

        CommentPollChoice.decrease_vote_count(poll, self.user)
        self.assertEqual(CommentPollChoice.objects.get(pk=self.choice.pk).vote_count, 0)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice.pk).vote_count, 1)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice2.pk).vote_count, 1)

        CommentPollChoice.objects.filter(pk=choice.pk).update(is_removed=True)
        CommentPollChoice.decrease_vote_count(poll, self.user)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice.pk).vote_count, 1)
        self.assertEqual(CommentPollChoice.objects.get(pk=choice2.pk).vote_count, 0)

    def test_poll_choice_update_or_create_many(self):
        """
        Should create or update many choices for a given poll
        """
        choice_raw = {"poll_name": "foo", "number": 2, "description": "2 bar"}
        CommentPollChoice.update_or_create_many(
            comment=self.comment, choices_raw=[choice_raw]
        )
        choice = CommentPollChoice.objects.all().order_by("pk").last()
        self.assertTrue(CommentPollChoice.objects.get(pk=self.choice.pk).is_removed)
        self.assertEqual(choice.poll, self.poll)
        self.assertEqual(choice.number, 2)
        self.assertEqual(choice.description, "2 bar")
        self.assertFalse(choice.is_removed)

        # Update
        choice_raw2 = {"poll_name": "foo", "number": 1, "description": "1 bar"}
        choice_raw["description"] = "2 foo"
        CommentPollChoice.update_or_create_many(
            comment=self.comment, choices_raw=[choice_raw, choice_raw2]
        )
        choice_updated = CommentPollChoice.objects.all().order_by("pk").last()
        self.assertFalse(CommentPollChoice.objects.get(pk=self.choice.pk).is_removed)
        self.assertEqual(choice_updated.poll, self.poll)
        self.assertEqual(choice_updated.number, 2)
        self.assertEqual(choice_updated.description, "2 foo")
        self.assertFalse(choice.is_removed)

    def test_poll_choice_update_or_create_many_removed_poll(self):
        """
        Should raise an Exception if poll is_removed
        """
        CommentPoll.objects.filter(pk=self.poll.pk).update(is_removed=True)
        choice_raw = {"poll_name": "foo", "number": 2, "description": "2 bar"}
        self.assertRaises(
            KeyError,
            CommentPollChoice.update_or_create_many,
            comment=self.comment,
            choices_raw=[choice_raw],
        )


class PollUtilsTest(TestCase):
    def setUp(self):
        utils.cache_clear()
        self.user = utils.create_user()
        self.category = utils.create_category()
        self.topic = utils.create_topic(category=self.category, user=self.user)
        self.comment = utils.create_comment(
            topic=self.topic, comment_html="<poll name=foo>"
        )

        self.poll = CommentPoll.objects.create(
            comment=self.comment, name="foo", title="my poll"
        )
        self.choice = CommentPollChoice.objects.create(
            poll=self.poll, number=1, description="choice 1"
        )
        self.choice = CommentPollChoice.objects.create(
            poll=self.poll, number=2, description="choice 2"
        )

    def test_post_render_static_polls(self):
        """
        Should render the static polls
        """
        comment_html = post_render_static_polls(self.comment)
        self.assertTrue("my poll" in comment_html)

        comment_parts = [
            line.strip()
            for line in strip_tags(comment_html).splitlines()
            if line.strip()
        ]
        self.assertEqual(
            comment_parts,
            [
                "my poll",
                "#1 choice 1",
                "#2 choice 2",
                "Name: foo, choice selection: from 1 up to 1, mode: default",
            ],
        )

    def test_post_render_static_polls_many(self):
        """
        Should render the many static polls
        """
        comment = utils.create_comment(
            topic=self.topic, comment_html="<poll name=foo>\n<poll name=bar>"
        )
        CommentPoll.objects.create(comment=comment, name="foo", title="my poll")
        CommentPoll.objects.create(comment=comment, name="bar", title="my other poll")

        comment_html = post_render_static_polls(comment)
        self.assertTrue("my poll" in comment_html)
        self.assertTrue("my other poll" in comment_html)

    def test_post_render_static_polls_close_at(self):
        """
        Should render the static polls with close_at
        """
        now = timezone.now()
        comment = utils.create_comment(topic=self.topic, comment_html="<poll name=foo>")
        CommentPoll.objects.create(
            comment=comment, name="foo", title="my poll", close_at=now
        )

        comment_html = post_render_static_polls(comment)
        self.assertTrue("close at:" in comment_html)
        self.assertTrue("Name:" in comment_html)
        self.assertTrue("choice selection:" in comment_html)
        self.assertTrue("mode:" in comment_html)

    def test_post_render_static_polls_no_poll(self):
        """
        Should render the comment with no poll
        """
        comment = utils.create_comment(topic=self.topic, comment_html="foo")
        comment_html = post_render_static_polls(comment)
        self.assertEqual(comment_html, "foo")

    def test_post_render_static_polls_removed_poll(self):
        """
        Should not render removed polls
        """
        self.poll.is_removed = True
        self.poll.save()
        comment_html = post_render_static_polls(self.comment)
        self.assertEqual(comment_html, "<poll name=foo>")
